Roll20 uses cookies to improve your experience on our site. Cookies enable you to enjoy certain features, social sharing functionality, and tailor message and display ads to your interests on our site and others. They also help us understand how our site is being used. By continuing to use our site, you consent to our use of cookies. Update your cookie preferences .
×
Create a free account

[Thinking About] New Style Of Random Dungeon Generator With Forced Perspective and "Mini-Map."

1411861217

Edited 1411865194
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter
Just sent over a new token pack I made for publication on the market. The plan is to develop a new style of random dungeon generator with forced perspective and "mini-map." (Its not out yet... but look for it soon.) Its based on the idea that map areas are divided in 19x19 unit grid, with bird's eye view perspective to each area. Quick walk-through on manual setup of the pack.... should help with thinking through how an API should be coded. 1) Add a new map page. 2) Set the page side to a multiple of 14 (84 for example: 14 x 5 = 84) 3) Make sure grid is enabled and the grid size is 14 units. Your map should look like the above. If you do not have Dynamic Lighting you can skip this step. 4) Now go to the Dynamic Lighting Layer. Outline the first column with a rectangle. Repeat for every other column. Your map should look like the above. 5) Now we do the rows. Outline the first row with a rectangle. Repeat for every other row Your map should look like the above. Note: That process can be repeated on the GM layer. Its not necessary, however if you are using fog of war only it can be helpful. 6) Now you can drop in the major feature of your dungeon. Go to the map layer and drop in the Dungeon Depth tiles as you see fit. Flip and spin as needed. Be sure to be using only the “14x14” room and hallway tiles. They should size properly since the page grip is 14 units. This a very simple and small dungeon. Note: The “black” off-map area will look like Dynamic Lighting or Fog of War to the players. This reduces the work you have to do for map setup. 7) Now you can add in other “14x14” features like stairs (up or down.) While still on the map layer, drop in the Dungeon Depth features you want. Flip and spin as needed. They should size properly since the page grid is 14 units. Now my dungeon has stairs down into this level and stairs even further down. 8) Now change the grid units to 12. . This will break the map alignment, however we will be changing it again. This just lets Roll20 do all the working of properly sizing the tiles for us. Now we can bring in “inner” walls. I brought in a corner wall section and tee wall section I plan to use. 9) Now change the grid units to 1. And move the “inner” walls into place.They fit centered on the room. The map is almost complete! 10) Going back the Dynamic Lighting layer (if you have the feature.) Added the few lines need. 11) Turn on Dynamic Lighting and/or Fog of War. Set the background to black. You can make your grid transparent, the map is already perfectly aligned to the to 1x1 units. 12) Go to the token layers and you are finished! 13) Just add players! All done! As you can see… my brave dwarven cleric’s perspective is forced to whatever room he is in! ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Now... for the API. Because this works in a grid.... 1) The dynamic lines are largely known. 2) We can use a simple simple recursive algorithm to generate dungeon floor plans. 3) Additional details can be added in easily. And.... adding a mini-map is simple. 1) Keep track of what areas the players have entered. 2) Reserve an area of the map that is ~10% of size of the total map. Put a visible to all light source there. 3) Since we know what is in each 14x14 grid... its easy to reproduce the same images in the reserved space in a 2x2 grid. 4) Masked unexplored areas with a black 2x2 image above. 5) Remove the mask as the players progress. 6) You could even add in player tracking. Anyhow that is the idea. Worth while? Or any thoughts?
An issue with this style of mapping is measuring while moving... there are extra squares in between rooms that wouldn't normally be there with traditional top down mapping.
1411866012
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter
That is an issue I had not considered.... but Its also play style a bit too. Maybe? I guess... I know I don't deal with distance unless its close/important. I really got my head fixed on the grid and black off map coloring to make the dynamic lighting super simple to lay out. And realized that would make a min-map really easy to fake out. And then I needed tiles... and once I was in "grid" mode of thinking... well, I did forced perspective per grid. Still the tile pack doesn't matter ... as long as it's "grid." I think that is value here if anything because of the min-map. Something no-geomorphic BUT easy to code and light.
1412008678
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter
Setting a map size is easy, based on the above. Next would be required starting and ending locations. This could be random or set by placing maker tokens. The green box would be required to have stairs into the room (down into and/or up out of.) The red box would be requited to have a side door or at least on set of stairs in the opposite direction to the green. You could have corner starts or ends too. Next would be finding the critical path. What I have right now is the following shape types: Full Room Hall Corner Quarter So with those... have to have pathing code to do something like this. Would need to make sure this path has doors (added later) to complete the path. After that, you just need to step through the map until all tiles are accounted for. First pass start in the upper left square A1.... (like in Excel or "battleship") This square is "hang" without adjacent information... so skip... As you move through... A1, B1, C1, D1, E1.... A2, B2, C2... etc.) they all have enough information to make a random, choosing not to place a tile is part of the choose (not skipping, but randomly choosing not to place a tile. Returning to A1, there is now adjacent tile misinformation so something is picked. Critical doors and stairs and such are placed. After that is just addling door and any inner walls (if any) for the other rooms and tiles. Great thing about this... is you could make several level deep dungeon (more than one page... automatically and the stairs would match, map to map to map.)
1412010425
The Aaron
Roll20 Production Team
API Scripter
I think this would be really cool, even though I don't know that it would show up in any of my games. However, I could totally see creating a light weight board-game-esque project out of this, ala Mice & Mystics. +1 =D
1412129390
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter
Well.... that is a start..... but a long way to the end. At map size selection I make an x/y array to keep track of the tiles. DungeonDepthMapArray[x][y] All the "cells" have the proper "left" "top" coordinates... but "none" for tile name and "" for the URL {"name":"none","tileURL":"","xValue":1470,"yValue":4410} Once filled I update it. {"name":"Start","tileURL":"<a href="https://s3.amazonaws.com/files.d20.io/images/5781339/IoqT_z2YCp7i95bZC@N8UA/thumb.jpg?1412113478&quot;,&quot;xValue&quot;:1470,&quot;yValue&quot;:5390" rel="nofollow">https://s3.amazonaws.com/files.d20.io/images/5781339/IoqT_z2YCp7i95bZC@N8UA/thumb.jpg?1412113478","xValue":1470,"yValue":5390</a>} Will likely have to add "spin", "vFlip" and "hFlip" to this.
1412173036

Edited 1412175982
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter
Finding my path.... next review each path tile in order and change to properly rotated tiles. I need the complete path to do this in an array.... so I can "look" one step ahead. ["Right","Right","Right","Right","Up","Up","Up"] So from "Start" I move right... going right. ( [ "Right" , "Right" ,"Right","Right","Up","Up","Up"] ) ...then I move right ( [ "Right" , "Right" , "Right" ,"Right","Up","Up","Up"] ) ...then I move right ( [ "Right" , "Right" , "Right" ," Right" ,"Up","Up","Up"] ) ...then I move right ( [ "Right" , "Right" , "Right" , "Right" , "Up" ,"Up","Up"] ).... I would be in the corner "place holder" in the bottom right. ...then I move up ( [ "Right" , "Right" , "Right" , "Right" , "Up" , "Up" ,"Up"] ) ...then I move up ( [ "Right" , "Right" , "Right" , "Right" , "Up" , "Up" , "Up "] )... right below the "End"... Just need random but valid tile swaps as I step along this path.
1412207589
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter
Such a pain... have to look forward and backward to make sure "flip" doesn't break the path.
1412211577
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter
Ok.... this was getting away from me.... But I think I have way to simplify this. Will on work in the corners of the path in some cases. That is helpful... You have to "look" forward to spot the "end" and "look" back to avoid: I was comparing the last tiles spin and flip to the next tile to be placed spin and flip. That got CRAZY. But... I just care about the side of the last tile.... which can be though of as being on of these three. Now the new tile can be randomly spun and flipped... then turned until we have at least 1/2 wall alignment.
1412212554

Edited 1412219900
I might suggest doing something along the lines of a breadth-first search from the start to the end: Determine connecting faces (maybe even a bit each for "clockwise of middle" and "counterclockwise of middle" doors, so an L-shaped tile in "L" orientation would have a "counterclockwise of middle" connection upwards and a "clockwise of middle" connection right) of each tile. Initialize your grid, considering empty squares fully-connected (excluding those connections on the edges of the map). For each square, mark each of its connections as a path to the end. From here on out, you'll be placing tiles. Each time you place a tile, update the square's connection info and visit each neighboring square. Remove any of the neighbor's connections which don't match the placed tile, and unmark the matching connections as possible paths to the end. If this leaves the neighbor without any connections marked as paths to the end, visit each of the neighbor's neighbors and unmark their matching connections as possible paths to the end; recurse as necessary (note, you only need to recurse if some connection's mark was removed; spaces which were already unconnected need no further work). Place your start and end tiles, updating the connection info of the relevant grid spaces (at this point you know that there's a possible path from start to end, because every other space is a fully-connected empty space). Place random tiles adjacent to each connection. Restrict your random options to those tiles with matching connections, and only those tiles which maintain at least one potential path from start to end. Update the connection info for the relevant spaces. Repeat the previous step until no connections remain. Possibly something like this (untested, so probably not quite right): CONNECTION_UL = (1&lt;&lt;0); //numbered around the circle so that geometric rotation can be done by bitwise rotation CONNECTION_UR = (1&lt;&lt;1); CONNECTION_RU = (1&lt;&lt;2); CONNECTION_RD = (1&lt;&lt;3); CONNECTION_DR = (1&lt;&lt;4); CONNECTION_DL = (1&lt;&lt;5); CONNECTION_LD = (1&lt;&lt;6); CONNECTION_LU = (1&lt;&lt;7); MASK_U = CONNECTION_UL | CONNECTION_UR; MASK_R = CONNECTION_RU | CONNECTION_RD; MASK_D = CONNECTION_DR | CONNECTION_DL; MASK_L = CONNECTION_LD | CONNECTION_LU; ALL_DIRECTIONS = [MASK_U, MASK_R, MASK_D, MASK_L]; TILE_BIG_ROOM = 0xff; //all connection bits, setting by number instead of symbolic constant for brevity TILE_SMALL_ROOM = CONNECTION_UR | CONNECTION RU; //small room in top right corner TILE_HALL = CONNECTION_LU | CONNECTION_RU; //hall across top ALL_TILES = [TILE_BIG_ROOM, TILE_SMALL_ROOM, TILE_HALL]; function rotateClockwise(tile){ return (tile &gt;&gt; 6) | ((tile &lt;&lt; 2) & 0xff); } function rotateCounterClockwise(tile){ return (tile &gt;&gt; 2) | ((tile &lt;&lt; 6) & 0xff); } function flip(tile){ return (tile &gt;&gt; 4) | ((tile &lt;&lt; 4) & 0xff); } function directionCount(c){ return (c & MASK_U ? 1 : 0) + (c & MASK_R ? 1 : 0) + (c & MASK_D ? 1 : 0) + (c & MASK_L ? 1 : 0); } var grid = []; var squaresToFill = []; var startPoint = []; var endPoint = []; function initGrid(w, h){ for (var i = 0; i &lt; w; i++){ grid[i] = []; for (var j = 0; j &lt; h; j++){ connections = 0; if (i &gt; 0){ connections |= CONNECTION_LU | CONNECTION_LD; } if (i &lt; w - 1){ connections |= CONNECTION_RU | CONNECTION_RD; } if (j &gt; 0){ connections |= CONNECTION_UL | CONNECTION_UR; } if (j &lt; h - 1){ connections |=CONNECTION_DL | CONNECTION_DR; } grid[i][j] = {'connections': connections, 'paths': connections, 'filled': 0}; } } } function addSquareToFill(x, y, requiredConnections){ if ((x &lt; 0) || (x &gt;= grid.length) || (y &lt; 0) || (y &gt;= grid[x].length)){ return; } //square outside of grid if (grid[x][y].filled){ return; } //square already filled for (var i = 0; i &lt; squaresToFill.length; i++){ if ((squaresToFill[i][0] == x) && (squaresToFill[i][1] == y)){ squaresToFill[i][2] |= requiredConnections; return; } } squaresToFill.push([x, y, requiredConnections]); } function updateUnplaced(x, y, connections, mask){ if ((x &lt; 0) || (x &gt;= grid.length) || (y &lt; 0) || (y &gt;= grid[x].length)){ return; } //square outside of grid if (grid[x][y].filled){ return; } //tile already placed, no more work to do here if (!(grid[x][y].paths & mask)){ return; } //square doesn't depend on connections from this direction var invMask = ~mask & 0xff; grid[x][y].connections &= (connections | invMask); grid[x][y].paths &= invMask; if (directionCount(grid[x][y].paths) &gt; 1){ return; } //square still connected in multiple other directions var flippedConnections = flip(grid[x][y].connections); updateUnplaced(x - 1, y, flippedConnections, MASK_R); updateUnplaced(x + 1, y, flippedConnections, MASK_L); updateUnplaced(x, y - 1, flippedConnections, MASK_D); updateUnplaced(x, y + 1, flippedConnections, MASK_U); } function placeTile(tile, x, y){ grid[x][y].filled = 1; grid[x][y].connections &= tile; grid[x][y].paths = 0; var flippedConnections = flip(grid[x][y].connections); if (grid[x][y].connections & MASK_L){ addSquareToFill(x - 1, y, flippedConnections & MASK_R); } if (grid[x][y].connections & MASK_R){ addSquareToFill(x + 1, y, flippedConnections & MASK_L); } if (grid[x][y].connections & MASK_U){ addSquareToFill(x, y - 1, flippedConnections & MASK_D); } if (grid[x][y].connections & MASK_D){ addSquareToFill(x, y + 1, flippedConnections & MASK_U); } updateUnplaced(x - 1, y, flippedConnections, MASK_R); updateUnplaced(x + 1, y, flippedConnections, MASK_L); updateUnplaced(x, y - 1, flippedConnections, MASK_D); updateUnplaced(x, y + 1, flippedConnections, MASK_U); } function placeEndpoints(){ startPoint = [randomInteger(grid.length) - 1, randomInteger(grid[0].length) - 1]; endPoint = [randomInteger(grid.length) - 1, randomInteger(grid[0].length) - 1]; placeTile(TILE_BIG_ROOM, startPoint[0], startPoint[1]); placeTile(TILE_BIG_ROOM, endPoint[0], endPoint[1]); } function candidateValid(candidate, requiredConnections, outboundPaths){ var overlap = candidate & requiredConnections for (var mask in ALL_DIRECTIONS){ if ((requiredConnections & mask) && !(overlap & mask)){ return 0; //candidate doesn't make a required connection } } //candidate makes all required connections if (!outboundPaths){ return 1; } //no outbound paths are necessary, so candidate is valid return candidate & outboundPaths; //valid iff candidate matches at least one outboundPath } function fillSquare(x, y, requiredConnections){ var outboundPaths = 0; if ((x &gt; 0) && (!grid[x-1][y].filled) && (grid[x-1][y].paths)){ outboundPaths |= MASK_L; } if ((x &lt; grid.length - 1) && (!grid[x+1][y].filled) && (grid[x+1][y].paths)){ outboundPaths |= MASK_R; } if ((y &gt; 0) && (!grid[x][y-1].filled) && (grid[x][y-1].paths)){ outboundPaths |= MASK_U; } if ((y &gt; grid[x].length - 1) && (!grid[x][y+1].filled) && (grid[x][y+1].paths)){ outboundPaths |= MASK_D; } for (var i = 0; i &lt; squaresToFill.length; i++){ if (grid[squaresToFill[i][0]][squaresToFill[i][1]].paths){ outboundPaths = 0; break; } } var candidates = []; for (var i = 0; i &lt; ALL_TILES.length; i++){ var candidate = ALL_TILES[i]; for (var j = 0; j &lt; 4; j++){ if (candidateValid(candidate, requiredConnections, outboundPaths)){ candidates.push(candidate); } candidate = rotateClockwise(candidate); } } var tileIndex = randomInteger(candidates.length) - 1; placeTile(candidates[tileIndex], x, y); } function fillTiles(){ while (squaresToFill.length &gt; 0){ square = squaresToFill.shift(); fillSquare(square[0], square[1], square[2]); } } ///// // // main entry point: generateMap(width, height) // ///// function generateMap(w, h){ grid = []; //re-initialize globals in case this gets called multiple times squaresToFill = []; startPoint = []; endPoint = []; initGrid(w, h); placeEndpoints(); fillTiles(); } Grr markdown. There went all my formatting...
Really looking forward to the tileset and generator. I loved your other script and am using it a lot. Just hoping I won't have to set up a 100+ rollable table for it =p
1412246694
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter
manveti Thanks! That is the sort of gold nugget I was hoping for! xNidhogg No...this is the cool thing about this one.... the pack has a lot of tiles for people that want to set up a specific map. But... I think this API will need under ten (since doors and other featrures will be overlaid.)
1412247776
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter
Is really.... (bold strike through is upper left) 1 |1 0|1 "H" flip makes it... 1 |1 0|1 "H" flip and "V" flip make it... 0 |1 1|1 With the flipping done.... Its just a shift register for spin...0 spin would be. 0 111 90 degree spin from that would be... 1 011 Top side would be 10 Right side would be 01 Bottom would be 11 Left side would be 11
1412263843
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter
Need to add the doors on the path still... but for now working to fill in the map by stepping through and looking for tile placement.
1412350611
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter
Still tweaking... pushed out the start and end to avoid large maps with small dungeons. And have increase the randomization of the critical path. Just have to work on "L" shapes.. they sometimes don't play well with others.
1412352015

Edited 1412352644
The Aaron
Roll20 Production Team
API Scripter
I'd probably encode with each tile a number that represents the 8 possible exit locations (or more, if you're counting 3 per side instead of 2 per side), then do some bitwise operations do check validity for adjacent neighbors. Edit : you could also use that information when dynamically drawing dynamic lighting and doors, since your ties are extremely regular. =D Edit 2 : Really you could describe your tiles with only 4 bits of information, specifying which of the 4 quadrants the tile takes up. your squares would be 0b1111, or 0xf, your horizontal rooms would be 0b1100 and 0b0011, and your L shaped room with the red circle could be 0b1110. You could then define some masks to represent the 4 blocks, and do some ANDing to see if the two tiles are compatible.
1412352401
The Aaron
Roll20 Production Team
API Scripter
Are each of your tiles going to have specific doors, or will you add doors where desired with a tile over the top?
1412353427
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter
You do know I have google some of the things you say (i.e. bitwise operations" :P ) For the generator I was planning to drop in the doors, this will keep the rollable table as small as possible, and set up easy. And yes... the dynamic lighting and doors should be easy... the map is just an "egg carton" I just drop in eggs and then need to make sure they are turned properly. My poorly coded working draft. <a href="https://gist.github.com/BaldarSilveraxe/746534f5d9" rel="nofollow">https://gist.github.com/BaldarSilveraxe/746534f5d9</a>...
1412353639
The Aaron
Roll20 Production Team
API Scripter
cool! Looking forward to playing with it this weekend. =D
1412363897
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter
I think I get the concept... but i have little to no idea how to effectively code with the concept. Starting at top left this room is "1110" The tiles are 14 roll20 map units square. Or 980 px with a 490 center point. So the array for the entire map is just this. buildMapArray = function() { for (i = 1; i &lt; Math.floor((DungeonDepthMapHeight/14) + 1); i++) { DungeonDepthMapArray[i] = new Array(Math.floor(DungeonDepthMapWidth/14)) for (j = 1; j &lt; Math.floor((DungeonDepthMapWidth/14) + 1); j++) { DungeonDepthMapArray[i][j] = {}; DungeonDepthMapArray[i][j] = { name: "none", tileURL: "", xValue: Math.floor(((j - 1) * 980) + 490), yValue: Math.floor(((i - 1) * 980) + 490), spin: 0, flipv: false, fliph: false, walls: "9999", }; }; }; }; This should care for all tile placement. Getting the critical path works well enough.. As I do the above... I get a path array placePathTile = function(xCurrent,yCurrent,xWasAt,yWasAt) { DungeonDepthMapArray[yCurrent][xCurrent].name = "Holder"; DungeonDepthMapArray[yCurrent][xCurrent].tileURL = findObjs({ _rollabletableid: DungeonDepthTableId, _type: "tableitem", name: "Holder"})[0].get("avatar").replace("med.jpg?","thumb.jpg?"); DungeonDepthMapArray[yCurrent][xCurrent].spin = 0; var stepDirection; if(xCurrent &lt; xWasAt){stepDirection = "Left";}; if(xCurrent &gt; xWasAt){stepDirection = "Right";}; if(yCurrent &lt; yWasAt){stepDirection = "Up";}; if(yCurrent &gt; yWasAt){stepDirection = "Down";}; pathToTake.push ({direction: stepDirection, xPath: xCurrent, yPath: yCurrent}) }; But bitwise placement... along that path... for just three tiles? Give me fits. :-/
The logic in the fillSquare and candidateValid functions in what I posted above goes something like this: determine possible/required connections in each direction: rotate each adjacent tile 180 degrees, bitwise AND with the appropriate side mask no connection required for empty adjacent squares for each tile type { for each orientation { make sure that each side matches up with a connection if necessary make sure there's at least one outgoing connection towards the next tile in your path if both of the above are satisfied, add this orientation of this tile type to the list of candidates } } randomly select a tile from the list of candidates Rotating a tile can be accomplished by bitwise rotation of its binary representation. A tile matches the necessary connections if (tile & requirements & side_mask) is nonzero. A tile has an outgoing connection on a side if (tile & side_mask) is nonzero. Using your representation above, TOP_MASK = 0xc, RIGHT_MASK = 0x6, BOTTOM_MASK = 0x3, and LEFT_MASK = 0x9 (note that you can also get this by starting with one of the masks and getting the others by rotation).
1412379878
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter
Working through your code, however I honest don't understand all the syntax. For example... (tile &gt;&gt; 6) | ((tile &lt;&lt; 2) & 0xff); I have no clue what that is :P 0xff is hex for a binary value? I was first introduced to bitwise in this thread :P
1412386516

Edited 1412905585
tile &gt;&gt; 6 means shift tile 6 bits to the right (it takes the top two bits and puts them into the bottom two bits). tile &lt;&lt; 2 shifts tile 2 bits to the left. 0xff is eight 1 bits: it keeps the left shift (which would otherwise put our top two bits into the ninth and tenth bits) restricted to just the bottom eight bits that we care about. I did some reworking and made a functioning prototype. It works as a standalone web page, and expects the following images to be in its directory (where N ranges from 0 to 3, rotating the room 90 degrees clockwise each step): bigN.png: copies of the big room smallN.png: the small room, starting in the top right (so small0 is top right, small1 is bottom right, etc.) hallN.png: the hallway, starting across the top cornerN.png: the L-shaped room, starting in the top and right (so corner2 is the one oriented like an L) Edit: I'm tired of that huge block of irrelevant code taking up screen space; I moved it to pastebin: <a href="http://pastebin.com/xipU0k23" rel="nofollow">http://pastebin.com/xipU0k23</a>
1412386622

Edited 1412392515
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter
Many thanks! And check your PMs. I need all the help I can get... because my reach is once again exceeding my grasp. Edit; Got your HTML working... That is really slick!
Here's a prototype that works ingame: <a href="https://github.com/manveti/dungen/blob/master/dung" rel="nofollow">https://github.com/manveti/dungen/blob/master/dung</a>... Type "!dungen [width] [height] [optional: tile size in pixels]" to generate a [width]x[height] map at the top left of the page with the player flag. There isn't any real error-checking, so make sure your map is big enough (and tile size should probably be a multiple of 70). I'm not sure why, but the placed tiles have a really big clickable area (way beyond the extents of the tokens themselves). Still trying to figure that one out.
1412505646

Edited 1412506127
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter
That is awesome! LOL.... In 1/8th of the lines of code it was taking me to even TRY to do this.
Nice work! Unfortunately I run into trouble every time I try and run the script. I get the following error: 'Sandbox closed due to error.'
I'll have to put in some error checking when I get a chance, but my guess would be that it's not letting you use the image URLs from my library. You can test for that either by uploading your own images and substituting in those URLs, or by adding an error message if we fail to create the tile: immediately after the 'var tile = createObj(...)' statement, add 'if(!tile){log("Failed to create tile with URL="+tileUrl+", size="+tileSize+", rotation=" (map[i][j].orientation * 90) + ", page ID=" + Campaign().get("playerpageid"));}'; if you see the message in the log, verify that you can see the image in a browser, that size is a multiple of 70, that rotation is 0, 90, 180, or 270, and that page ID is nonempty. If that's not it, I could also see problems happening if the start and end are the same (another error check not present in the prototype). This is less likely to happen the larger your map; if it happens a few times in a row using, say, a 5x5 map, either this isn't it or you're REALLY unlucky. This can be tested by adding 'log("Start point: "+startPoint[0]+","+startPoint[1]);' after the 'var startPoint = ...' line and the same thing (but replacing "start" with "end") after the 'var endPoint = ...' line.
1412809806

Edited 1412809880
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter
Would it be best if the tiles had the doors? The issue I see with that is in greatly increases the numbers of map tile (they are all made but it would be such a pain to load them into a table or hard code the URLs... or if someone wanted to make tile just for this... they would have t make a lot more tiles.) At any wall touch you would want one door... easier to delete doors than add doors when polishing up the map is also the thinking.
The doors are probably better as overlays (I didn't check, but I assume that door graphic you have is set up so that it can be flipped and/or rotated to work in any of the 8 possible door slots). We already know which potential doors are touching, so we could keep track of those spots (or possibly randomly select one if neighboring tiles touch on both spots) and put doors there at the end.
1412847107
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter
The door I set over was 2x5 (Roll20 grid units) jpg. This one is a full tile png <a href="https://drive.google.com/file/d/0B7d47ZZymuXPTTd2b" rel="nofollow">https://drive.google.com/file/d/0B7d47ZZymuXPTTd2b</a>...
I realized another possible source of troubles: my prototype wasn't packaged in any way, so it was pretty likely to collide with other API scripts. I fixed that up and added some error-checking (and retry logic for placing the end, so start and end are guaranteed to be different). I also added the ability to toggle sparse map generation: the original would stop placing tiles once an area was cut off from the exit and couldn't form an independent path there. With sparse generation turned off, we'll continue to place tiles as long as they can connect somewhere, so generated maps will have fewer holes. I'll look into placing doors over the weekend, and maybe add the ability to configure the images (select a token and run a command to use it as a particular tile) so it won't have to ship with magic paths.
1412950456

Edited 1412956134
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter
DunGen: !dungen WIDTH HEIGHT [TILE SIZE] Generate a WIDTHxHEIGHT dungeon from square tiles of the specified size (in pixels) TILE SIZE defaults to 980; it should generally be a multiple of 70 !dungen sparse [on|off] Display or set status of sparse map generation If sparse is on, no tiles will be added to paths which cannot connect to the exit DunGen: Sparse Map Generation: on Sparse Map Generation: off Works great... The sparse option is really nice. I did run into one error (super trivial.) Line 278 in the showHelp function.. typo.... "sendChat" not "sentChat." This is really good code, gradually working through it were I can understand the syntax. Having this simple dynamic line layout and "egg carton" room structure makes it so easy to "spice" up the dungeon. Some simple "floor" overlays dropped in below.
1413026536
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter
function basicDynamicLighting(width,height){ for (var i = DunGen.TILE_SIZE; i &lt; width*DunGen.TILE_SIZE; i = i + DunGen.TILE_SIZE){ var pathTop = 0; var pathLeft = i; var pathPath = "[[\"M\",0,0],[\"L\",0," + height*DunGen.TILE_SIZE + "]]"; createPath(pathTop,pathLeft,pathPath) } for (var i = DunGen.TILE_SIZE; i &lt; height*DunGen.TILE_SIZE; i = i + DunGen.TILE_SIZE){ var pathTop = i; var pathLeft = 0; var pathPath = "[[\"M\",0,0],[\"L\"," + width*DunGen.TILE_SIZE + ",0]]"; createPath(pathTop,pathLeft,pathPath) } } function createPath(pathTop,pathLeft,pathPath){ createObj("path",{ fill: "#00ff00", stroke: "#00ff00", stroke_width: 5, width: 0, height: 0, top: pathTop, left: pathLeft, scaleX:1, scaleY:1, layer: "walls", _path: pathPath, pageid: Campaign().get("playerpageid") }); }; Played around with the above... adds the basic Dynamic Lighting paths.
I've got the doors working with the full-size overlay, but it's kind of a pain to do any manual editing with them. I think I'll probably modify it to use the door-only image (and just add a warning that if tile size is not a multiple of 14 then rounding in the math for placing the doors might mean that some of them end up off by a pixel). I haven't looked into the performance implications, but it might be a good idea to do the lines for dynamic lighting on a per-tile basis so people can open up sight lines through doors (or even "perforate" the sides, splitting each into 5 lines of lengths 3, 2, 4, 2, 3; then people could just delete the 2's for doors).
1413108822

Edited 1413113439
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter
After looking at this and playing around with it.... Aren't the doors really just a few simple 10 x 10 tiles? Each tile + intersection would get an appropriate "Door S tile"? This will make selecting them and editing them.... Because you the "Room Tile" and the "Doors Tiles" are accessible to click on ! At those meet points... there can only be the following. And... if the room tile are in a roll-able table.... and the doors are in a roll-able table... you can leverage the Roll20 "multi-sided" controls. I looked at the Dynamic Light... I think you are right... That much I should be able to fix on my own :P
Smaller, door-only door overlays are working now, as are dynamic lighting lines. I've got it making yellow lines for walls and green for doors. It looks a little strange because of the perspective (you have to pick a height at which to draw the lines; I chose to do it at floor level, but that makes the lines look wrong when you look at the dynamic lighting layer), but it's pretty easy do delete out the green lines where you want open doors.
1413237826
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter
Looks good and works great! The advantage/disadvantage of thick walls.... Might be worth it to have "super thick" Dynamic Lines..... lots of rooms on the tiles.. and from either side gives that "doorway" feel. But its just a nuance.. Great script!
Oh, that's a good idea. Maybe something like 1/14 of the tile size, with a minimum of maybe two or three pixels just to be sure there's something reasonably visible even when zoomed out a bit (although really, if you're generating a map with tiles less than 14 pixels on a side you probably deserve broken dynamic lighting lines).
Wider lines are in now. They're still based on ground-level offsets, just because that's easiest to get perfect. That effective height can be changed by reducing DOOR_OFFSET and increasing DOOR_WIDTH (by less than what DOOR_OFFSET was reduced by). It looks like DOOR_OFFSET should be reduced by about one per 2% of the way up to ceiling height, and DOOR_WIDTH should be increased by about half that.
So I just went to use this in my Friday night campaign and realized that the width of the dynamic lighting lines doesn't seem to have any effect at all: the light/LoS blocking only takes place along the center of the line. I'll have to replace the lines with rectangles when I get a chance.
1413563708

Edited 1413591286
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter
I think you are right, DL needs a rectangle, seems to ignore line thickness. I finally got the pack on the market: <a href="https://marketplace.roll20.net/browse/set/384/dung" rel="nofollow">https://marketplace.roll20.net/browse/set/384/dung</a>... (The delay was entirely my fault.)
Updated to use rectangles. I'll replace the start/end tiles with full-room tiles with stairs down/up overlays and remove the image URLs this weekend, and remove the images I used for testing from my library so the URLs currently in the script stop working once I'm not using an auto-generated dungeon in my Friday night campaign. Hopefully I'll get a chance to add some setup commands (list tile URLs, use image URL from selected token for specified tile, etc.) too, but if not I'll at least put some comments in the script explaining how to set up the necessary tile URLs.
1413591363
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter
I think its great. And should work with any "box" shaped token set. Really great script. No way I could have coded that. many thanks for all your effort.
The script now allows/requires the user to set tile/overlay images, and I've removed the images whose URLs were in previous versions of the script from my library so it doesn't act as a back door for free tile images. At some point I'll build in a way to set some variables in the script to hold default tile URLs, so people can modify their copy of the script to point at their images and not have to set them for each campaign. It also now uses stairs overlays instead of those special "start" and "end" tiles.
1413967505

Edited 1413967525
I now it's probably a lot to ask, but can we get a wiki page on how to set everything up? Especially now that your pack was released Stephen (will buy as soon as script is done). Or is the script not at a public-releasable stage currently?
1414061023

Edited 1414061265
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter
Hope this helps: 1) Basic tiles needed can be found here: <a href="https://drive.google.com/file/d/0B7d47ZZymuXPQjRGc" rel="nofollow">https://drive.google.com/file/d/0B7d47ZZymuXPQjRGc</a>... 2) I started a blank campaign and use the Transmogrifier to placed finished maps in player campaigns. 3) Set up 8 macros in the macro bar: createMap = !dungen 7 7 BigRoom = !dungen setimage BigRoom Corner = !dungen setimage Corner Door = !dungen setimage Door Hall = !dungen setimage Hall SmallRoom = !dungen setimage SmallRoom StairsDown= !dungen setimage StairsDown StairsUp= !dungen setimage StairsUp 4) The campaign has two maps : 5) The Tiles Map is used to setup the tiles Map size is 48w x 12h and the Grid size = 12 Units Place the basic tiles (don't worry about the "stretched" look of the doors.) 6) One by one select each tile and click the appropriate macro. Example Select "BigRoom" and click the BigRoom macro Chat will output something like: 7) The DungeonDepth map should be 98w by 98 h for a 7 tile by 7 tile map ("!dungen 7 7) 8) Click the "createMap" macro. You should get a random map with Dynamic Lighting. (Note in some rare cases the "z" position of overlays is wrong, you may have to push a tile or two to the back.) The Code can be found here: manveti said: Here's a prototype that works ingame: <a href="https://github.com/manveti/dungen/blob/master/dung" rel="nofollow">https://github.com/manveti/dungen/blob/master/dung</a>... Type "!dungen [width] [height] [optional: tile size in pixels]" to generate a [width]x[height] map at the top left of the page with the player flag. There isn't any real error-checking, so make sure your map is big enough (and tile size should probably be a multiple of 70).
Sick stuff, bought the pack to support you. Thanks for the explanation.
1414236065
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter
Thanks. manveti saved me from myself, His code is really slick! I am still toying around with an idea for a "mini" map. (Seen below and to the right.) I am just reading through the map layer looking for "room" tiles and creating smaller version of them to the right. The idea would be to save the players from zooming and scrolling... but to really do that, they would need to be able call the mini-map to the chat pane or somewhere besides the map... I think. Best thing would be to get the URLs in an array as the map was made i.e. maptile[x][y] = tile.url Then another array that keeps track of the explored areas i.e. if(explored[x][y] == false){ //use all black tile } else { //use maptile[x][y] = tile.url }; Then players could call up the miniMap... just not sure where to put it or if its worth it.
1414484459

Edited 1414484773
You could wire into the map-drawing code, right above or below the first call to createObj. You could even create two new tokens for each room: one for the room in the mini-map, and one a black overlay that gets deleted when the room is explored. Incidentally, I just made a couple of minor user-friendliness improvements: Added a "tiles" subcommand which gives a name and description for each tile so tile setup isn't so full of guess-and-check magic Integrated with my new Shell module ( <a href="https://github.com/manveti/roll20/blob/master/shell.js" rel="nofollow">https://github.com/manveti/roll20/blob/master/shell.js</a> ), which (among other things) adds a general "!help" command to give a brief description of each command registered with it. It also has some access-control features which I'll leverage later to make the "!dungen" command GM-only (edit: I just remembered that Shell commands are GM-only by default, so that's already done). Adding an easy way to paste the image URLs into some constants near the top of the file so you don't have to redo the tile setup each campaign is still in the pipeline.
I added a structure near the top of the file with a constant for each necessary image, which will act as a default URL if the relevant URL isn't already set in the campaign. I also modified the script to generate dynamic light lines even where there aren't actually tiles, so that we don't get the diagonal sight lines across empty tiles like you see in the instruction image a few posts up.